import { Nodes } from '@knno/dom';
import { h, jsxGen, JSXProps } from '@knno/dom/jsx';
import { DATA } from '../components/symbol';
import { renderIf } from './utils';
import { autoHideScrollBar } from '../components/global.css';
import { navMenuStyle } from './navMenu/navMenu.css';
import SVGApp from '@mdi/svg/svg/apps.svg';
import { Icon } from './icon';
import SVGchevronDown from '@mdi/svg/svg/chevron-down.svg';
import SVGchevronUp from '@mdi/svg/svg/chevron-up.svg';
import { Foldable } from './foldable';

type MenuItem = Record<string, unknown>;

type MenuItemColumns = {
	key?: number | string;
	children?: number | string;
	icon?: number | string;
	text?: number | string;
	line?: number | string;
	opened?: number | string;
};

type MenuItemInfo = { id: string; path: string; item: MenuItem; parent?: MenuItem };

const CHILDREN = 'children';

export interface NavMenuProps extends JSXProps<NavMenu> {
	columns?: MenuItemColumns;
	items: MenuItem[];
	onMenuItemClick?: (item: MenuItem, path: string) => void;
	selected?: MenuItem;
	selectedPath?: string;
	/**
	 * Only one group can be expanded at the same time.
	 */
	singleGroupMode?: boolean;
	aniDuration?: number;
}

type MenuGroup = { foldable: Foldable; caption: Nodes };

export class NavMenu extends Nodes {
	[DATA]: {
		columns?: MenuItemColumns;
		items: MenuItem[];
		onMenuItemClick?: (item: MenuItem, path: string) => void;
		selected: MenuItem | null;
		selectedId: string | null;
		singleGroupMode?: boolean;
		groups: (MenuGroup | null)[];
		aniDuration: number;
	};
	constructor(attrs: NavMenuProps) {
		super(<div class={[navMenuStyle, autoHideScrollBar]}></div>);
		this[DATA] = {
			items: attrs.items,
			columns: attrs.columns,
			onMenuItemClick: attrs.onMenuItemClick,
			selected: null,
			selectedId: null,
			singleGroupMode: attrs.singleGroupMode,
			groups: [],
			aniDuration: attrs.aniDuration ?? 250,
		};
		if (!attrs.selected && attrs.selectedPath) {
			this.selectedPath(attrs.selectedPath);
		}
		this.render();
		jsxGen(this, attrs, []);
	}

	render() {
		const textKey = this[DATA].columns?.text ?? 'text';
		const childrenKey = this[DATA].columns?.children ?? CHILDREN;
		const iconKey = this[DATA].columns?.icon ?? 'icon';
		const openedKey = this[DATA].columns?.opened ?? 'opened';
		this[DATA].groups.length = 0;
		this.html('').append(
			...this[DATA].items.map((item, idx) => {
				let group: Foldable;
				let caption: Nodes;
				const hasChildren = item[childrenKey] instanceof Array && item[childrenKey].length > 0;
				if (!hasChildren) {
					this[DATA].groups.push(null);
				}
				const opened =
					(item[openedKey] as boolean | undefined) ??
					(hasChildren && (item[childrenKey] as Array<MenuItem>).some((sub) => sub === this[DATA].selected));
				return (
					<div class={hasChildren ? 'm1 group' : 'm1'}>
						<div
							name={`g${idx}`}
							class={item === this[DATA].selected ? ['caption', 'selected'] : 'caption'}
							ref={(c) => (caption = c)}
							tabindex="0"
							onkeypress={function (evt) {
								if (evt.key === 'Enter') {
									this.emit(new Event('click'));
								}
							}}
							onclick={() => {
								if (hasChildren) {
									const opened = !group.opened();
									if (opened) {
										this.expandGroup(item);
									} else {
										this.shrinkGroup(item);
									}
								} else {
									const info = this.getItemInfo(item);
									if (info) {
										this.selectedByInfo(info);
										if (this[DATA].singleGroupMode) {
											this.shrinkOthers(null);
										}
										this[DATA]?.onMenuItemClick?.(item, info.path);
									}
								}
							}}
						>
							<Icon class="icon" icon={(item[iconKey] as string | undefined) ?? SVGApp} />
							<span class="text">{item[textKey]}</span>
							{renderIf(hasChildren, <Icon icon={opened ? SVGchevronDown : SVGchevronUp} class="arrow" />)}
						</div>
						{renderIf(hasChildren, () => {
							const children = item[childrenKey] as MenuItem[];
							return (
								<Foldable
									class="children"
									opened={opened}
									aniDuration={this[DATA].aniDuration}
									ref={(f) => {
										group = f;
										this[DATA].groups.push({ caption, foldable: f });
									}}
								>
									{...children.map((sub, si) => (
										<div
											name={`g${idx}-s${si}`}
											tabindex="0"
											class={sub === this[DATA].selected ? ['caption', 'selected'] : 'caption'}
											onkeypress={function (evt) {
												if (evt.key === 'Enter') {
													this.emit(new Event('click'));
												}
											}}
											onclick={() => {
												const info = this.getItemInfo(sub);
												if (info) {
													this.selectedByInfo(info);
													this[DATA]?.onMenuItemClick?.(sub, info.path);
												}
											}}
										>
											{sub[textKey]}
										</div>
									))}
								</Foldable>
							);
						})}
					</div>
				);
			})
		);
	}

	expandGroup(item: MenuItem) {
		const idx = this[DATA].items.indexOf(item);
		if (idx >= 0) {
			const g = this[DATA].groups[idx];
			if (g) {
				g.caption.query(':scope>svg.arrow').detach();
				g.caption.append(<Icon icon={SVGchevronDown} class="arrow" />);
				g.foldable?.opened(true);
				if (this[DATA].singleGroupMode) {
					this.shrinkOthers(g);
				}
			}
		}
	}

	private shrinkOthers(group: MenuGroup | null) {
		this[DATA].groups.forEach((other) => {
			if (other !== group) {
				if (other) {
					other.caption.query(':scope>svg.arrow').detach();
					other.caption.append(<Icon icon={SVGchevronUp} class="arrow" />);
					other.foldable?.opened(false);
				}
			}
		});
	}

	shrinkGroup(item: MenuItem) {
		const idx = this[DATA].items.indexOf(item);
		if (idx >= 0) {
			const g = this[DATA].groups[idx];
			if (g) {
				g.caption.query(':scope>svg.arrow').detach();
				g.caption.append(<Icon icon={SVGchevronUp} class="arrow" />);
				g.foldable?.opened(false);
			}
		}
	}

	items(): MenuItem[];
	items(items: MenuItem[]): this;
	items(items?: MenuItem[]): MenuItem[] | this {
		if (items === undefined) {
			return this[DATA].items;
		} else {
			this[DATA].items = items;
			this.render();
			return this;
		}
	}

	getItemInfo(item: MenuItem): MenuItemInfo | null {
		const childrenKey = this[DATA].columns?.children ?? CHILDREN;
		const keyKey = this[DATA].columns?.key ?? 'key';
		for (let g = 0; g < this[DATA].items.length; g++) {
			const gi = this[DATA].items[g];
			const children = gi[childrenKey] as MenuItem[] | undefined;
			const key = (gi[keyKey] as string | undefined) ?? `g${g}`;
			const hasChildren = children instanceof Array && children.length > 0;
			if (gi === item && !hasChildren) {
				return { item, id: `g${g}`, path: `${key}` };
			}
			if (hasChildren) {
				for (let s = 0; s < children.length; s++) {
					const si = children[s] as MenuItem;
					if (si === item) {
						const subKey = (si[keyKey] as string | undefined) ?? `s${s}`;
						return { item, id: `g${g}-s${s}`, path: `${key}/${subKey}`, parent: gi };
					}
				}
			}
		}
		return null;
	}

	selectedPath(): string | null;
	selectedPath(path: string | null): this;
	selectedPath(path?: string | null): string | null | this {
		if (path === undefined) {
			if (this[DATA].selected) {
				const info = this.getItemInfo(this[DATA].selected);
				if (info) {
					return info.path;
				}
			}
			return null;
		} else {
			if (path === null) {
				this.selected(null);
			} else {
				const keyKey = this[DATA].columns?.key ?? 'key';
				const arr = path.split('/');
				if (arr[0]) {
					for (let gi = 0; gi < this[DATA].items.length; gi++) {
						const g = this[DATA].items[gi];
						const childrenKey = this[DATA].columns?.children ?? CHILDREN;
						const children = g[childrenKey] as MenuItem[] | undefined;
						const hasChildren = children instanceof Array && children.length > 0;
						const key = (g[keyKey] as string | undefined) ?? `g${gi}`;
						if (key === arr[0]) {
							if (arr.length > 1 && hasChildren) {
								for (let si = 0; si < children.length; si++) {
									const s = children[si] as MenuItem;
									const subKey = (s[keyKey] as string | undefined) ?? `s${si}`;
									if (subKey === arr[1]) {
										this.selected(s);
										return this;
									}
								}
							} else if (!hasChildren) {
								this.selected(g);
								return this;
							}
						}
					}
				}
			}
			this.selected(null);
			return this;
		}
	}

	private selectedByInfo(info: MenuItemInfo | null) {
		if (this[DATA].selectedId) {
			this.query(`div.caption[name="${this[DATA].selectedId}"]`).removeClass('selected');
		}
		if (info) {
			if (info.parent) {
				this.expandGroup(info.parent);
			}
			this[DATA].selectedId = info.id;
			this[DATA].selected = info.item;
			this.query(`div.caption[name="${this[DATA].selectedId}"]`).addClass('selected');
		} else {
			this[DATA].selectedId = null;
			this[DATA].selected = null;
		}
	}

	selected(): MenuItem | null;
	selected(item: MenuItem | null): this;
	selected(item?: MenuItem | null): this | MenuItem | null {
		if (item === undefined) {
			return this[DATA].selected;
		} else {
			const info = item ? this.getItemInfo(item) : null;
			this.selectedByInfo(info);
			return this;
		}
	}
}
